/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package modeloprediccion;

import java.sql.*;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.HashMap;
import motorprediccion.*;

/**
 *
 * @author dani
 */
public class ModeloPrediccion extends Modelo{
    private HashMap parametros;
    private Recorrido recorrido;
    private HashMap<Integer,ArrayList<Prediccion>> predicciones;
    private ArrayList<Integer> x;
    private ArrayList<Float> y;
    private Double m,b;

    public ModeloPrediccion(Recorrido r) throws SQLException {
        // Inicializador del modelo
        predicciones = new HashMap<Integer,ArrayList<Prediccion>>();
        x = new ArrayList<Integer>(0);
        y = new ArrayList<Float>(0);
        m = new Double(0); b = new Double(0);
        recorrido = r;
        parametros = new HashMap();
        cargarTiemposBase();
    }

    public void procesarDatos(ArrayList<DatoProcesado> nuevosDatos) throws SQLException {
    //Procesa las nuevas posiciones de las unidades y calcula los tiempos y errores
        for(int i=0; i < nuevosDatos.size(); i++) {
            ParametrosParada pp = (ParametrosParada) parametros.get(nuevosDatos.get(i).getParada().getId());

            Double errorBase = nuevosDatos.get(i).getTiempoSegundos() - pp.getTiempoBase();
            Double errorPrediccion = nuevosDatos.get(i).getTiempoSegundos() - pp.getPrediccion();

            pp.actualizarParametros(errorBase, errorPrediccion);
        }

        estimarTiempos(nuevosDatos.get(0));
        calcularErroresPrediccion(nuevosDatos);
    }

        public Double obtenerPrediccion(Integer linea, Integer origen, Integer destino) {
        //Funcion que suma los tiempos desde la parada origen a la parada destino
        Parada i = this.getRecorridos(linea).obtenerParadaOrden(origen);
        Double tiempoEspera = new Double(0);

        while(i.getOrden() <= destino) {
            ParametrosParada pp = (ParametrosParada) parametros.get(i.getId());
            tiempoEspera += pp.getPrediccion();
            //tiempoEspera += (int) pp.getTiempoBase();
            i = this.recorrido.obtenerParadaSiguiente(i.getId());
        }

        return tiempoEspera;
    }

    private void cargarTiemposBase() throws SQLException {
        Connection conexionBD = null;

          try {
            conexionBD = DriverManager.getConnection("jdbc:postgresql://localhost/sistemaprediccion",
                                            "postgres", "postgres");
          }
          catch (SQLException se) {
            System.out.println("Couldn't connect: print out a stack trace and exit.");
            se.printStackTrace();
            System.exit(1);
          }

        Statement s = null;
        try {
          s = conexionBD.createStatement();
        }
        catch (SQLException se) {
          System.out.println("Error en la BD");
          se.printStackTrace();
          System.exit(1);
        }

        String consulta = "SELECT idparada, promedio, desstd, orden FROM motorprediccion.promedios order by orden asc;";
        ResultSet resultado = null;
        try {
          resultado = s.executeQuery(consulta);
        }
        catch (SQLException se) {
          System.out.println("SQL Error");
          se.printStackTrace();
          System.exit(1);
        }
        //Ver posibilidad de unificar excepciones

        while(resultado.next()) {
            Integer idparada = resultado.getInt("idparada");
            Double promedio = resultado.getDouble("promedio");
            ParametrosParada parametro = new ParametrosParada(recorrido.obtenerParada(idparada), promedio);
            this.parametros.put(idparada,parametro);
        }
    }

    private void estimarTiempos(DatoProcesado dato) {
        //funcion que calcula los tiempos y los acumula en predicciones.
        Parada siguiente = recorrido.obtenerParadaSiguiente(dato.getParada().getId());

        ArrayList<Prediccion> p = (ArrayList<Prediccion>)predicciones.get(dato.getIdcolectivo());

        if(p == null)
        {
            predicciones.put(dato.getIdcolectivo(), new ArrayList<Prediccion>(0));
            p = (ArrayList<Prediccion>)predicciones.get(dato.getIdcolectivo());
        }

        for(Integer i = recorrido.obtenerOrden(dato.getParada()); i < recorrido.getCantidadParadas(); i++)
        {
            Prediccion prediccion = new Prediccion(dato.getParada(),siguiente,dato.getHoraArribo(),dato.getIdcolectivo());
            GregorianCalendar horaArribo = new GregorianCalendar();

            Double distancia = new Double(recorrido.obtenerOrden(siguiente)-recorrido.obtenerOrden(dato.getParada()));
            Double ajusteError = this.calcularCorreccion(distancia);

            horaArribo.setTimeInMillis(prediccion.getHoraPrediccion().getTimeInMillis()+ new Double(
                    (obtenerPrediccion(recorrido.getId(),dato.getParada().getOrden(), siguiente.getOrden())*1000-ajusteError*1000)).longValue());
            prediccion.setHoraArriboEstimada(horaArribo);

            p.add(prediccion);
            siguiente = recorrido.obtenerParadaSiguiente(siguiente.getId());
        }
    }

    private void calcularErroresPrediccion(ArrayList<DatoProcesado> datos) throws SQLException {
        //funcion que corrige los errores, los va calculando y los almacena
        ArrayList<Prediccion> p = (ArrayList<Prediccion>)predicciones.get(datos.get(0).getIdcolectivo());

        if(p != null) {
            for(Integer i = 0; i < datos.size(); i++) {
                ArrayList<Prediccion> prediccionesyErrores = new ArrayList<Prediccion>(0);

                int j = p.size()-1;
                Long toleranciaMilis = new Long(50 * 60 * 1000); // 50 minutos
                Long diferenciaTiempo = (datos.get(i).getHoraArribo().getTimeInMillis() -
                        p.get(j).getHoraArriboEstimada().getTimeInMillis());

                while(j > 0) {
                    diferenciaTiempo = (datos.get(i).getHoraArribo().getTimeInMillis() -
                        p.get(j).getHoraArriboEstimada().getTimeInMillis());
                    if(datos.get(i).getParada().getId() == p.get(j).getDestino().getId()) {
                        if(diferenciaTiempo < toleranciaMilis) {
                            p.get(j).setHoraArriboReal(datos.get(i).getHoraArribo());
                            extraerDatosParaRegresionLineal(p.get(j));
                            prediccionesyErrores.add(p.remove(j));
                        }
                    }
                    j--;
                }
                this.guardarPrediccion(prediccionesyErrores);
            }
            calcularRectaAjuste();
        }
    }

    public void guardarPrediccion(ArrayList<Prediccion> p) throws SQLException {
        //Esta funcion guarda las nuevas estimaciones en la Base de Datos
        Connection conexionBD = null;

          try {
            conexionBD = DriverManager.getConnection("jdbc:postgresql://localhost/sistemaprediccion",
                                            "postgres", "postgres");
          }
          catch (SQLException se) {
            System.out.println("Couldn't connect: print out a stack trace and exit.");
            se.printStackTrace();
            System.exit(1);
          }

        Statement s = null;
        try {
          s = conexionBD.createStatement();
        }
        catch (SQLException se) {
          System.out.println("Error en la BD");
          se.printStackTrace();
          System.exit(1);
        }

        String consulta="";

        for(int i=0; i<p.size(); i++) {
            Integer origen = this.recorrido.obtenerOrden(p.get(i).getOrigen());
            Integer destino = this.recorrido.obtenerOrden(p.get(i).getDestino());

            consulta += "INSERT INTO motorprediccion.prediccionesyerrores("+
            "idcolectivo, paradaorigen, paradadestino, paradasdistancia, "+
            " horaestimacion, arriboestimado, arriboreal) "+
            "VALUES ("+
                p.get(i).getIdColectivo() + ", "+
                origen + ", "+
                destino + ", "+
                (destino-origen)+", "+
                "to_timestamp("+p.get(i).getHoraPrediccion().getTimeInMillis()/1000+"),"+
                "to_timestamp("+p.get(i).getHoraArriboEstimada().getTimeInMillis()/1000+"),"+
                "to_timestamp("+p.get(i).getHoraArriboReal().getTimeInMillis()/1000+"));\n";
        }

            if(consulta.length()>0) {
                try {
                  s.executeUpdate(consulta);
                }
                catch (SQLException se) {
                  System.out.println("SQL Error");
                  se.printStackTrace();
                  System.exit(1);
                }
            }
        conexionBD.close();
        }

    private void extraerDatosParaRegresionLineal(Prediccion prediccion) {
        //funcion que mantiene el limite de los datos para la regresion lineal
        x.add(recorrido.obtenerOrden(prediccion.getDestino()) - recorrido.obtenerOrden(prediccion.getOrigen()));
        y.add((float)(-prediccion.getHoraArriboReal().getTimeInMillis()+
                prediccion.getHoraArriboEstimada().getTimeInMillis())/1000);

        while(x.size()>300) {
            x.remove(0);
            y.remove(0);
        }
    }

    private void calcularRectaAjuste() {
        //Funcion que va calculando el corrector
        if(x.size()>100) {
            Double sumX = new Double(0), sumXY = new Double(0), sumXX = new Double(0), sumY = new Double(0);
            Integer n = x.size();

            for(int i = 0; i < n; i++) {
                sumX += x.get(i);
                sumXY += x.get(i)*y.get(i);
                sumXX += x.get(i)*x.get(i);
                sumY += y.get(i);
            }

            m = (n*sumXY-sumX*sumY)/(n*sumXX-sumX*sumX);
            b = (sumY-m*sumX)/n;
        }
    }

    private Double calcularCorreccion(Double distancia) {
        //Dejar separado porque se puede dar un mejor modelo de correccion
        return (Double)(m*distancia+b);
    }

    public Integer procesarNuevosTiempos(ArrayList<DatoPosicional> nuevosDatos){
        ArrayList<DatoProcesado> datosProcesados ;
        HashMap colectivos = new HashMap();
        int cantidadIndefiniciones = 0, cantidadDefiniciones = 0;
        for(int i = 0; i < nuevosDatos.size(); i++) {
            try {
                int idcolectivo = nuevosDatos.get(i).getIdColectivo();
                System.out.println("Posicion"+nuevosDatos.get(i).getPosicion()+" Fecha"+nuevosDatos.get(i).getFecha().getTime().toString());
                if(colectivos.containsKey(idcolectivo)) {
                    Colectivo c = (Colectivo) colectivos.get(idcolectivo);
                    int posLinea = 0;
                    for (int j=0; j< recorridos.size();j++){
                        if (c.getRecorrido()== recorridos.get(j).getId())
                            posLinea = j;
                    }
                    datosProcesados = c.procesarNuevoDato(nuevosDatos.get(i));
                    if(c.obtenerParadaActual() != null) {
                        System.out.println(c.obtenerParadaActual().getId()+ "  "+ recorridos.get(posLinea).obtenerOrden(c.obtenerParadaActual()));
                        System.out.println("Distancia a la parada "+nuevosDatos.get(i).calcularDistancia(c.obtenerParadaActual().getPosicion().getLatitude(),c.obtenerParadaActual().getPosicion().getLongitude()));
                        cantidadDefiniciones++;
                    }
                    else {
                        System.out.println("Ninguna");
                        cantidadIndefiniciones++;
                    }
                }
                else {
                    colectivos.put(idcolectivo, new Colectivo(idcolectivo,this.getRecorridos(this.getIdRecorridoUnidad(idcolectivo))));
                    Colectivo c = (Colectivo) colectivos.get(idcolectivo);
                    datosProcesados = c.procesarNuevoDato(nuevosDatos.get(i));
                    int posLinea = 0;
                    for (int j=0; j< recorridos.size();j++){
                        if (c.getRecorrido()== recorridos.get(j).getId())
                            posLinea = j;
                    }
                    if(c.obtenerParadaActual() != null) {
                        System.out.println(c.obtenerParadaActual().getId()+ "  "+ recorridos.get(posLinea).obtenerOrden(c.obtenerParadaActual()));
                        cantidadDefiniciones++;
                    }
                    else {
                        System.out.println("Ninguna");
                        cantidadIndefiniciones++;
                    }
                }
                if(datosProcesados != null) 
                    this.procesarDatos(datosProcesados);
            }
            catch (SQLException se) {
                System.out.println("Error en la BD");
                se.printStackTrace();
                System.exit(1);
            }
        }
        System.out.println();
        System.out.println("Def, indef, %aciertos");
        System.out.println(cantidadDefiniciones+","+cantidadIndefiniciones+" "+cantidadDefiniciones*100/(cantidadDefiniciones+cantidadIndefiniciones));
        return cantidadDefiniciones;
    }
}
